# encoding=utf-8
import math
import time
from typing import List
from seg_system.application_config import ApplicationConfig
from seg_common.LoggingService import ConsoleService
import numpy as np
from seg_system.Vascular.service.VascularToolsForBatch.VascularEachProcessor import pyVascular
from multiprocessing.dummy import Pool as ThreadPool


class VascularForBatchBase:
    """VascularForBatchBase
        - vascular算法的基类， 暂时实现的是拼接矩阵的实现思路
        - kwargs算法行为调整：
            - use_cxx: True/False, default True, 忽略全局，对于局部算法进行选择
            - name: str, default "", 希望输出的算法名称
            - one_by_one: True/False, default False, 是否逐一进行解析(这时候推荐stride为0)
            - 其余的kwargs由于快速变化的需求而添加，需要根据实际情况确定

        note:
            本部分的设计主要采用冗余方案，即python部分主要研究生提供，个人根据python方案实现c++
            目的就是方便后续一开始的部署，算法快速切换
    """
    # static init
    cxxPyVascular = pyVascular.vascular()  # c++包可能没处理好，只能全局导入一次
    output_time_cost = ApplicationConfig.SystemConfig.DEBUGGER
    theadingPoolObj = ThreadPool(ApplicationConfig.SystemConfig.VASCULAR_NUM_WORKER)

    def process(self, big_matrix: np.ndarray, usable_matrix: np.ndarray,
                each_matrix_shape: tuple, stride: int, file_name: list, save_path: str, **kwargs):
        """
            :param big_matrix 拼接矩阵
            :param usable_matrix 矩阵有效位置映射
            :param each_matrix_shape 拼接矩阵时，单个矩阵的size信息
            :param stride: 拼接矩阵中矩阵间隔像素
            :param file_name: 需要保存的文件名列表
            :param save_path: 用于拼接出真正的存储路径

            全主流程完成3件事情：
                1. 效率统计以及上报
                2. 结果处理
                3. 结果保存
        """
        use_cxx = kwargs.get('use_cxx', None)
        if use_cxx is None:
            use_cxx = ApplicationConfig.SystemConfig.USER_CUDA_CV

        if VascularForBatchBase.output_time_cost:
            start = time.perf_counter()

        if use_cxx:
            process_out = self.process_with_cxx(big_matrix, usable_matrix, each_matrix_shape, stride, **kwargs)
        else:
            process_out = self.process_with_python(big_matrix, usable_matrix, each_matrix_shape, stride, **kwargs)

        batch_return = self.batch_save(file_name, save_path, process_out, **kwargs)

        if VascularForBatchBase.output_time_cost:
            end = time.perf_counter()
            name = kwargs.get('name', '')
            ConsoleService.console_log("{} algorithm time cost is: {}".format(name, end - start))

        return batch_return

    def process_with_python(self, big_matrix: np.ndarray, usable_matrix: np.ndarray,
                            each_matrix_shape: tuple, stride: int, **kwargs):
        raise NotImplementedError

    def process_with_cxx(self, big_matrix: np.ndarray, usable_matrix: np.ndarray,
                         each_matrix_shape: tuple, stride: int, **kwargs):
        raise NotImplementedError

    def batch_save(self, file_name: list, save_path: str, process_output, **kwargs):
        raise NotImplementedError

    def hilditch(self, img):
        # get shape
        H, W, C = img.shape

        # prepare out image
        out = np.zeros((H, W), dtype=np.int32)
        out[img[..., 0] > 0] = 1

        # inverse
        out = 1 - out

        while True:
            s1 = []
            s2 = []

            # step 1 ( rasta scan )
            for y in range(1, H - 1):
                for x in range(1, W - 1):

                    # condition 1
                    if out[y, x] > 0:
                        continue

                    # condition 2
                    f1 = 0
                    if (out[y - 1, x + 1] - out[y - 1, x]) == 1:
                        f1 += 1
                    if (out[y, x + 1] - out[y - 1, x + 1]) == 1:
                        f1 += 1
                    if (out[y + 1, x + 1] - out[y, x + 1]) == 1:
                        f1 += 1
                    if (out[y + 1, x] - out[y + 1, x + 1]) == 1:
                        f1 += 1
                    if (out[y + 1, x - 1] - out[y + 1, x]) == 1:
                        f1 += 1
                    if (out[y, x - 1] - out[y + 1, x - 1]) == 1:
                        f1 += 1
                    if (out[y - 1, x - 1] - out[y, x - 1]) == 1:
                        f1 += 1
                    if (out[y - 1, x] - out[y - 1, x - 1]) == 1:
                        f1 += 1

                    if f1 != 1:
                        continue

                    # condition 3
                    f2 = np.sum(out[y - 1:y + 2, x - 1:x + 2])
                    if f2 < 2 or f2 > 6:
                        continue

                    # condition 4
                    # x2 x4 x6
                    if (out[y - 1, x] + out[y, x + 1] + out[y + 1, x]) < 1:
                        continue

                    # condition 5
                    # x4 x6 x8
                    if (out[y, x + 1] + out[y + 1, x] + out[y, x - 1]) < 1:
                        continue

                    s1.append([y, x])

            for v in s1:
                out[v[0], v[1]] = 1

            # step 2 ( rasta scan )
            for y in range(1, H - 1):
                for x in range(1, W - 1):

                    # condition 1
                    if out[y, x] > 0:
                        continue

                    # condition 2
                    f1 = 0
                    if (out[y - 1, x + 1] - out[y - 1, x]) == 1:
                        f1 += 1
                    if (out[y, x + 1] - out[y - 1, x + 1]) == 1:
                        f1 += 1
                    if (out[y + 1, x + 1] - out[y, x + 1]) == 1:
                        f1 += 1
                    if (out[y + 1, x] - out[y + 1, x + 1]) == 1:
                        f1 += 1
                    if (out[y + 1, x - 1] - out[y + 1, x]) == 1:
                        f1 += 1
                    if (out[y, x - 1] - out[y + 1, x - 1]) == 1:
                        f1 += 1
                    if (out[y - 1, x - 1] - out[y, x - 1]) == 1:
                        f1 += 1
                    if (out[y - 1, x] - out[y - 1, x - 1]) == 1:
                        f1 += 1

                    if f1 != 1:
                        continue

                    # condition 3
                    f2 = np.sum(out[y - 1:y + 2, x - 1:x + 2])
                    if f2 < 2 or f2 > 6:
                        continue

                    # condition 4
                    # x2 x4 x8
                    if (out[y - 1, x] + out[y, x + 1] + out[y, x - 1]) < 1:
                        continue

                    # condition 5
                    # x2 x6 x8
                    if (out[y - 1, x] + out[y + 1, x] + out[y, x - 1]) < 1:
                        continue

                    s2.append([y, x])

            for v in s2:
                out[v[0], v[1]] = 1

            # if not any pixel is changed
            if len(s1) < 1 and len(s2) < 1:
                break

        out = 1 - out
        out = out.astype(np.uint8) * 255

        return out

    def getSkeletonIntersection(self, skeleton, use_thread: bool = False):
        """ Given a skeletonised image, it will give the coordinates of the intersections of the skeleton.

        Keyword arguments:
        skeleton -- the skeletonised image to detect the intersections of

        Returns:
        List of 2-tuples (x,y) containing the intersection coordinates

        """
        # A biiiiiig list of valid intersections             2 3 4
        # These are in the format shown to the right         1 C 5
        #                                                    8 7 6
        validIntersection = [[0, 1, 0, 1, 0, 0, 1, 0], [0, 0, 1, 0, 1, 0, 0, 1], [1, 0, 0, 1, 0, 1, 0, 0],
                             [0, 1, 0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 0, 1, 0, 1], [1, 0, 0, 1, 0, 0, 1, 0],
                             [0, 1, 0, 0, 1, 0, 0, 1], [1, 0, 1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0, 1],
                             [0, 1, 0, 1, 0, 0, 0, 1], [0, 1, 0, 1, 0, 1, 0, 0], [0, 0, 0, 1, 0, 1, 0, 1],
                             [1, 0, 1, 0, 0, 0, 1, 0], [1, 0, 1, 0, 1, 0, 0, 0], [0, 0, 1, 0, 1, 0, 1, 0],
                             [1, 0, 0, 0, 1, 0, 1, 0], [1, 0, 0, 1, 1, 1, 0, 0], [0, 0, 1, 0, 0, 1, 1, 1],
                             [1, 1, 0, 0, 1, 0, 0, 1], [0, 1, 1, 1, 0, 0, 1, 0], [1, 0, 1, 1, 0, 0, 1, 0],
                             [1, 0, 1, 0, 0, 1, 1, 0], [1, 0, 1, 1, 0, 1, 1, 0], [0, 1, 1, 0, 1, 0, 1, 1],
                             [1, 1, 0, 1, 1, 0, 1, 0], [1, 1, 0, 0, 1, 0, 1, 0], [0, 1, 1, 0, 1, 0, 1, 0],
                             [0, 0, 1, 0, 1, 0, 1, 1], [1, 0, 0, 1, 1, 0, 1, 0], [1, 0, 1, 0, 1, 1, 0, 1],
                             [1, 0, 1, 0, 1, 1, 0, 0], [1, 0, 1, 0, 1, 0, 0, 1], [0, 1, 0, 0, 1, 0, 1, 1],
                             [0, 1, 1, 0, 1, 0, 0, 1], [1, 1, 0, 1, 0, 0, 1, 0], [0, 1, 0, 1, 1, 0, 1, 0],
                             [0, 0, 1, 0, 1, 1, 0, 1], [1, 0, 1, 0, 0, 1, 0, 1], [1, 0, 0, 1, 0, 1, 1, 0],
                             [1, 0, 1, 1, 0, 1, 0, 0]]
        image = skeleton.copy()
        """这里应该是 //, 否则数据对不上，但是做了以后效率大减
            2022.9.5修改为//，提高算法精度
        """
        image = image // 255
        intersections = list()

        for x in range(1, len(image) - 1):
            for y in range(1, len(image[x]) - 1):
                # If we have a white pixel
                if image[x][y] == 1:
                    neighbour = self.neighbours(x, y, image)
                    valid = True
                    if neighbour in validIntersection:
                        intersections.append((y, x))

        # Filter intersections to make sure we don't count them twice or ones that are very close together

        i_len = len(intersections)
        i = 0
        while i < i_len:
            point1 = intersections[i]
            j = 0
            while j < i_len:
                point2 = intersections[j]
                if (((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2) < 8 ** 2) and (point1 != point2):
                    intersections.remove(point2)
                    i_len -= 1
                    if j < i:
                        i -= 1
                else:
                    j += 1
            i += 1

        # intersections = list(set(intersections))
        return intersections

    def neighbours(self, x, y, image):
        """Return 8-neighbours of image point P1(x,y), in a clockwise order"""
        img = image
        x_1, y_1, x1, y1 = x - 1, y - 1, x + 1, y + 1
        return [img[x_1][y], img[x_1][y1], img[x][y1], img[x1][y1], img[x1][y], img[x1][y_1], img[x][y_1],
                img[x_1][y_1]]

    def degree(self, x1, y1, x2, y2):
        angle = 0.0
        dx = x2 - x1
        dy = y2 - y1
        if x2 == x1:
            angle = math.pi / 2.0
            if y2 == y1:
                angle = 0.0
            elif y2 < y1:
                angle = 3.0 * math.pi / 2.0
        elif x2 > x1 and y2 > y1:
            angle = math.atan(dx / dy)
        elif x2 > x1 and y2 < y1:
            angle = math.pi / 2 + math.atan(-dy / dx)
        elif x2 < x1 and y2 < y1:
            angle = math.pi + math.atan(dx / dy)
        elif x2 < x1 and y2 > y1:
            angle = 3.0 * math.pi / 2.0 + math.atan(dy / -dx)
        return angle * 180 / math.pi
